package com.dmall.opening.up.restful.application;

import com.dmall.opening.up.records.util.Jsons;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.CommonsRequestLoggingFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import static java.nio.charset.StandardCharsets.UTF_8;

public class RequestAndResponseLoggingFilter extends CommonsRequestLoggingFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(RequestAndResponseLoggingFilter.class);

    @Override
    protected boolean shouldLog(HttpServletRequest request) {
        return LOGGER.isInfoEnabled();
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        if (isAsyncDispatch(request)) {
            filterChain.doFilter(request, response);
        } else {
            doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
        }
    }

    protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
        filterChain.doFilter(request, response);
        logRequest(request);
        logResponse(request, response);
    }

    private void logResponse(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException {
        byte[] responseContent = response.getContentAsByteArray();
        if (responseContent.length > 0) {
            String message = request.getMethod() +
                    " " +
                    request.getRequestURI() +
                    ", response content: " + new String(responseContent, UTF_8);
            LOGGER.info(message);
        }
        response.copyBodyToResponse();
    }

    private void logRequest(ContentCachingRequestWrapper request) {
        byte[] requestContent = request.getContentAsByteArray();
        if (requestContent.length > 0) {
            String message = request.getMethod() +
                    " " +
                    request.getRequestURI() +
                    ", query: " + request.getQueryString() +
                    ", headers: " + headersToString(request) +
                    ", request content: " + new String(requestContent, UTF_8);
            LOGGER.info(message);
        }
    }

    private String headersToString(ContentCachingRequestWrapper request) {
        Map<String, String> headers = new HashMap<>();
        final Enumeration<String> keys = request.getHeaderNames();
        while (keys.hasMoreElements()) {
            final String key = keys.nextElement();
            headers.put(key, request.getHeader(key));
        }
        return Jsons.toJsonString(headers);
    }

    private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
        if (request instanceof ContentCachingRequestWrapper) {
            return (ContentCachingRequestWrapper) request;
        } else {
            return new ContentCachingRequestWrapper(request);
        }
    }

    private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
        if (response instanceof ContentCachingResponseWrapper) {
            return (ContentCachingResponseWrapper) response;
        } else {
            return new ContentCachingResponseWrapper(response);
        }
    }
}
